"""
File containing the class object containing the functions to handle binary_c version info.
This class will be used to extend the population object
NOTE: could these functions not just be normal functions rather than class methods? I see hardly any use of the self
"""
# pylint: disable=E0203
import copy
import os
from typing import Union
from binarycpython import _binary_c_bindings
from binarycpython.utils.functions import isfloat
[docs]class version_info:
"""
Class object containing the functions to handle binary_c version info.
This class will be used to extend the population object
"""
def __init__(self, **kwargs):
"""
Init function for the version_info class
"""
return
########################################################
# version_info functions
########################################################
[docs] def return_binary_c_version_info(self, parsed: bool = True) -> Union[str, dict]:
"""
Function that returns the version information of binary_c. This function calls the function
_binary_c_bindings.return_version_info()
Args:
parsed: Boolean flag whether to parse the version_info output of binary_c. default = False
Returns:
Either the raw string of binary_c or a parsed version of this in the form of a nested
dictionary
"""
#####
# Check if the headers were previously defined
found_prev_MACRO_HEADER = False
if "BINARY_C_MACRO_HEADER" in os.environ:
# the env var is already present. lets save that and put that back later
found_prev_MACRO_HEADER = True
prev_value_MACRO_HEADER = os.environ["BINARY_C_MACRO_HEADER"]
found_prev_DEFLIST_HEADER = False
if "BINARY_C_DEFLIST_HEADER" in os.environ:
# the env var is already present. lets save that and put that back later
found_prev_DEFLIST_HEADER = True
prev_value_DEFLIST_HEADER = os.environ["BINARY_C_DEFLIST_HEADER"]
#############
# set new headers
os.environ["BINARY_C_MACRO_HEADER"] = "macroxyz"
os.environ["BINARY_C_DEFLIST_HEADER"] = "deflist "
#############
# Get version_info
raw_version_info = _binary_c_bindings.return_version_info().strip()
#############
# delete current headers
del os.environ["BINARY_C_MACRO_HEADER"]
del os.environ["BINARY_C_DEFLIST_HEADER"]
#############
# put back previous headers
if found_prev_MACRO_HEADER:
os.environ["BINARY_C_MACRO_HEADER"] = prev_value_MACRO_HEADER
if found_prev_DEFLIST_HEADER:
os.environ["BINARY_C_DEFLIST_HEADER"] = prev_value_DEFLIST_HEADER
##############
# return (parsed) version info
if parsed:
parsed_version_info = self.parse_binary_c_version_info(raw_version_info)
return parsed_version_info
return raw_version_info
def _parse_binary_c_version_info_networks(self, networks):
"""
Function to parse the networks info
"""
networks_dict = {}
for el in networks:
network_dict = {}
split_info = el.split("Network ")[-1].strip().split("==")
network_number = int(split_info[0])
network_dict["network_number"] = network_number
network_info_split = split_info[1].split(" is ")
shortname = network_info_split[0].strip()
network_dict["shortname"] = shortname
if not network_info_split[1].strip().startswith(":"):
network_split_info_extra = network_info_split[1].strip().split(":")
longname = network_split_info_extra[0].strip()
network_dict["longname"] = longname
implementation = (
network_split_info_extra[1].strip().replace("implemented in", "")
)
if implementation:
network_dict["implemented_in"] = [
i.strip("()") for i in implementation.strip().split()
]
networks_dict[network_number] = copy.deepcopy(network_dict)
return networks_dict if networks_dict else None
def _parse_binary_c_version_info_isotopes(self, isotopes):
"""
Function to parse the isotopes info
"""
isotope_dict = {}
for el in isotopes:
split_info = el.split("Isotope ")[-1].strip().split(" is ")
isotope_info = split_info[-1]
name = isotope_info.split(" ")[0].strip()
# Get details
mass_g = float(
isotope_info.split(",")[0].split("(")[1].split("=")[-1][:-2].strip()
)
mass_amu = float(
isotope_info.split(",")[0].split("(")[-1].split("=")[-1].strip()
)
mass_mev = float(
isotope_info.split(",")[-3].split("=")[-1].replace(")", "").strip()
)
A = int(isotope_info.split(",")[-1].strip().split("=")[-1].replace(")", ""))
Z = int(isotope_info.split(",")[-2].strip().split("=")[-1])
#
isotope_dict[int(split_info[0])] = {
"name": name,
"Z": Z,
"A": A,
"mass_mev": mass_mev,
"mass_g": mass_g,
"mass_amu": mass_amu,
}
return isotope_dict if isotope_dict else None
def _parse_binary_c_version_info_argpairs(self, argpairs):
"""
Function to parse the argpairs info
"""
argpair_dict = {}
for el in sorted(argpairs):
split_info = el.split("ArgPair ")[-1].split(" ")
if not argpair_dict.get(split_info[0], None):
argpair_dict[split_info[0]] = {split_info[1]: split_info[2]}
else:
argpair_dict[split_info[0]][split_info[1]] = split_info[2]
return argpair_dict if argpair_dict else None
def _parse_binary_c_version_info_ensembles(self, ensembles):
"""
Function to parse the ensembles info
"""
ensemble_dict = {}
ensemble_filter_dict = {}
for el in ensembles:
split_info = el.split("Ensemble ")[-1].split(" is ")
if len(split_info) > 1:
if not split_info[0].startswith("filter"):
ensemble_dict[int(split_info[0])] = split_info[-1]
else:
filter_no = int(split_info[0].replace("filter ", ""))
ensemble_filter_dict[filter_no] = split_info[-1]
return ensemble_dict, ensemble_filter_dict
def _parse_binary_c_version_info_macros(self, macros):
"""
Function to parse the macros info
"""
param_type_dict = {
"STRING": str,
"FLOAT": float,
"MACRO": str,
"INT": int,
"LONG_INT": int,
"UINT": int,
}
macros_dict = {}
for el in macros:
split_info = el.split("macroxyz ")[-1].split(" : ")
param_type = split_info[0]
new_split = "".join(split_info[1:]).split(" is ")
param_name = new_split[0].strip()
param_value = " is ".join(new_split[1:])
param_value = param_value.strip()
# If we're trying to set the value to "on", check that
# it doesn't already exist. If it does, do nothing, as the
# extra information is better than just "on"
if param_name in macros_dict:
if macros_dict[param_name] == "on":
# update with better value
store = True
elif (
isfloat(macros_dict[param_name]) is False
and isfloat(param_value) is True
):
# store the number we now have to replace the non-number we had
store = True
else:
# don't override existing number
store = False
else:
store = True
if store:
# Sometimes the macros have extra information behind it.
# Needs an update in outputting by binary_c (RGI: what does this mean David???)
try:
macros_dict[param_name] = param_type_dict[param_type](param_value)
except ValueError:
macros_dict[param_name] = str(param_value)
return macros_dict if macros_dict else None
def _parse_binary_c_version_info_elements(self, elements):
"""
Function to parse the elements info
"""
# Fill dict:
elements_dict = {}
for el in elements:
split_info = el.split("Element ")[-1].split(" : ")
name_info = split_info[0].split(" is ")
# get isotope info
isotopes = {}
if not split_info[-1][0] == "0":
isotope_string = split_info[-1].split(" = ")[-1]
isotopes = {
int(split_isotope.split("=")[0]): split_isotope.split("=")[1]
for split_isotope in isotope_string.split(" ")
}
elements_dict[int(name_info[0])] = {
"name": name_info[-1],
"atomic_number": int(name_info[0]),
"amt_isotopes": len(isotopes),
"isotopes": isotopes,
}
return elements_dict if elements_dict else None
def _parse_binary_c_version_info_dt_limits(self, dt_limits):
"""
Function to parse the dt_limits info
"""
# Fill dict
dt_limits_dict = {}
for el in dt_limits:
split_info = el.split("DTlimit ")[-1].split(" : ")
dt_limits_dict[split_info[1].strip()] = {
"index": int(split_info[0]),
"value": float(split_info[-1]),
}
return dt_limits_dict if dt_limits_dict else None
def _parse_binary_c_version_info_units(self, unit, units):
"""
Function to parse the units info
TODO: i'm not sure that this parsing is correct
"""
units_dict = {}
for el in unit:
split_info = el.split("Unit ")[-1].split(",")
s = split_info[0].split(" is ")
if len(s) == 2:
long, short = [i.strip().strip('"') for i in s]
elif len(s) == 1:
long, short = None, s[0]
else:
self.vb_warning("Warning: Failed to split unit string {}".format(el))
to_cgs = (split_info[1].split())[3].strip().strip('"')
code_units = split_info[2].split()
code_unit_type_num = int(code_units[3].strip().strip('"'))
code_unit_type = code_units[4].strip().strip('"')
code_unit_cgs_value = code_units[9].strip().strip('"').strip(")")
units_dict[long] = {
"long": long,
"short": short,
"to_cgs": to_cgs,
"code_unit_type_num": code_unit_type_num,
"code_unit_type": code_unit_type,
"code_unit_cgs_value": code_unit_cgs_value,
}
# Add the list of units
for el in units:
el = el[7:] # removes "Units: "
units_dict["units list"] = el.strip("Units:")
return units_dict if units_dict else None
def _parse_binary_c_version_nucsyn_sources(self, nucsyn_sources):
"""
Function to parse the nucsyn_sources info
"""
# Fill dict
nucsyn_sources_dict = {}
for el in nucsyn_sources:
split_info = el.split("Nucleosynthesis source")[-1].strip().split(" is ")
nucsyn_sources_dict[int(split_info[0])] = split_info[-1]
return nucsyn_sources_dict if nucsyn_sources_dict else None
def _parse_binary_c_version_binary_c_error_codes(self, binary_c_error_codes):
"""
Function to parse the binary_c_error_codes info
"""
binary_c_error_codes_dict = {}
for el in binary_c_error_codes:
split_info = el.split("Error code")[-1].strip().split(" = ")
# extract info
number = int(split_info[1].split()[0])
name = str(split_info[0])
description = str(split_info[1].split()[1].replace('"', ""))
binary_c_error_codes_dict[number] = {
"number": number,
"name": name,
"description": description,
}
return binary_c_error_codes_dict if binary_c_error_codes_dict else None
def _parse_binary_c_version_deflists(self, deflists):
"""
Function to parse the deflists info
"""
deflist_dict = {}
for el in deflists:
split_info = el.split("deflist")[-1].strip().split()
# Extract data
deflist_type = split_info[0]
deflist_data = " ".join(split_info[1:])
# Check if current type is already known
if deflist_type not in deflist_dict:
deflist_dict[deflist_type] = {}
# store data in current type dict
deflist_data_split = deflist_data.split(" is ")
deflist_data_number = int(deflist_data_split[0])
deflist_data_name = str(deflist_data_split[1])
deflist_dict[deflist_type][deflist_data_number] = deflist_data_name
return deflist_dict if deflist_dict else None
[docs] def parse_binary_c_version_info(self, version_info_string: str) -> dict:
"""
Function that parses the binary_c version info. Long function with a lot of branches
Args:
version_info_string: raw output of version_info call to binary_c
Returns:
Parsed version of the version info, which is a dictionary containing the keys: 'isotopes' for isotope info, 'argpairs' for argument pair info (TODO: explain), 'ensembles' for ensemble settings/info, 'macros' for macros, 'elements' for atomic element info, 'DTlimit' for (TODO: explain), 'nucleosynthesis_sources' for nucleosynthesis sources, and 'miscellaneous' for all those that were not caught by the previous groups. 'git_branch', 'git_build', 'revision' and 'email' are also keys, but its clear what those contain.
"""
version_info_dict = {}
# Clean data and put in correct shape
splitted = version_info_string.strip().splitlines()
cleaned = {el.strip() for el in splitted if not el == ""}
##########################
# Network:
# Split off all the networks
networks = {el for el in cleaned if el.startswith("Network ")}
cleaned = cleaned - networks
# and parse the info
version_info_dict["networks"] = self._parse_binary_c_version_info_networks(
networks
)
##########################
# Isotopes:
# Split off all the isotopes
isotopes = {el for el in cleaned if el.startswith("Isotope ")}
cleaned -= isotopes
# and parse the info
version_info_dict["isotopes"] = self._parse_binary_c_version_info_isotopes(
isotopes
)
##########################
# Arg pairs:
# Split off all the argpairs
argpairs = {el for el in cleaned if el.startswith("ArgPair")}
cleaned -= argpairs
# and parse the info
version_info_dict["argpairs"] = self._parse_binary_c_version_info_argpairs(
argpairs
)
##########################
# ensembles:
# Split off all the ensembles
ensembles = {el for el in cleaned if el.startswith("Ensemble")}
cleaned -= ensembles
# and parse the info
(
ensemble_dict,
ensemble_filter_dict,
) = self._parse_binary_c_version_info_ensembles(ensembles)
version_info_dict["ensembles"] = ensemble_dict if ensemble_dict else None
version_info_dict["ensemble_filters"] = (
ensemble_filter_dict if ensemble_filter_dict else None
)
##########################
# macros:
# Split off of all the macros
macros = {el for el in cleaned if el.startswith("macroxyz")}
cleaned -= macros
# and parse the info
version_info_dict["macros"] = self._parse_binary_c_version_info_macros(macros)
##########################
# Elements:
# Split off all the elements
elements = {el for el in cleaned if el.startswith("Element")}
cleaned -= elements
# and parse the info
version_info_dict["elements"] = self._parse_binary_c_version_info_elements(
elements
)
##########################
# dt_limits:
# split off all the dt_limits
dt_limits = {el for el in cleaned if el.startswith("DTlimit")}
cleaned -= dt_limits
# and parse the info
version_info_dict["dt_limits"] = self._parse_binary_c_version_info_dt_limits(
dt_limits
)
##############################
# Units
# split off all the units
unit = {el for el in cleaned if el.startswith("Unit ")}
cleaned -= unit
units = {el for el in cleaned if el.startswith("Units: ")}
cleaned -= units
# and parse the info
version_info_dict["units"] = self._parse_binary_c_version_info_units(
unit, units
)
##########################
# Nucleosynthesis sources:
# Split off all the nucsyn sources
nucsyn_sources = {el for el in cleaned if el.startswith("Nucleosynthesis")}
cleaned -= nucsyn_sources
# and parse the info
version_info_dict[
"nucleosynthesis_sources"
] = self._parse_binary_c_version_nucsyn_sources(nucsyn_sources)
##########################
# binary_c_errors:
# Split off all the nucsyn sources
binary_c_error_codes = {el for el in cleaned if el.startswith("Error code")}
cleaned -= binary_c_error_codes
# and parse the info
version_info_dict[
"binary_c_error_codes"
] = self._parse_binary_c_version_binary_c_error_codes(binary_c_error_codes)
##########################
# deflist:
# Split off all the deflists
deflists = {el for el in cleaned if el.startswith("deflist")}
cleaned -= deflists
# and parse the info
version_info_dict["deflists"] = self._parse_binary_c_version_deflists(deflists)
##########################
# miscellaneous:
# All those that I didn't catch with the above filters. Could try to get some more out though.
misc_dict = {}
# Filter out git revision
git_revision = [el for el in cleaned if el.startswith("git revision")]
misc_dict["git_revision"] = (
git_revision[0].split("git revision ")[-1].replace('"', "")
)
cleaned -= set(git_revision)
# filter out git url
git_url = [el for el in cleaned if el.startswith("git URL")]
misc_dict["git_url"] = git_url[0].split("git URL ")[-1].replace('"', "")
cleaned -= set(git_url)
# filter out version
version = [el for el in cleaned if el.startswith("Version")]
misc_dict["version"] = str(version[0].split("Version ")[-1])
cleaned -= set(version)
git_branch = [el for el in cleaned if el.startswith("git branch")]
misc_dict["git_branch"] = (
git_branch[0].split("git branch ")[-1].replace('"', "")
)
cleaned -= set(git_branch)
build = [el for el in cleaned if el.startswith("Build")]
misc_dict["build"] = build[0].split("Build: ")[-1].replace('"', "")
cleaned -= set(build)
email = [el for el in cleaned if el.startswith("Email")]
misc_dict["email"] = email[0].split("Email ")[-1].split(",")
cleaned -= set(email)
other_items = {el for el in cleaned if " is " in el}
cleaned -= other_items
for el in other_items:
split = el.split(" is ")
key = split[0].strip()
val = " is ".join(split[1:]).strip()
if key in misc_dict:
misc_dict[key + " (alt)"] = val
else:
misc_dict[key] = val
misc_dict["uncaught"] = list(cleaned)
version_info_dict["miscellaneous"] = misc_dict if misc_dict else None
return version_info_dict
[docs] def minimum_stellar_mass(self):
"""
Function to return the minimum stellar mass (in Msun) from binary_c.
"""
if not hasattr(self, "_minimum_stellar_mass"):
self._minimum_stellar_mass = self.return_binary_c_version_info(parsed=True)[
"macros"
]["BINARY_C_MINIMUM_STELLAR_MASS"]
return self._minimum_stellar_mass